/**
 * @file lv_port_disp.c
 *
 */

#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_disp.h"
#include "lv_conf.h"
#include "drv_dpu.h"
// #include "dpu.h"

#include <aos/kernel.h>

extern  dpu_handle_t dpu_handle;
static lv_color_t *ver_buf = NULL;

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
/* Flush the content base on DPU */
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
#if 0//LV_USE_GPU
static void gpu_blend(lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa);
static void gpu_fill(lv_color_t * dest, uint32_t length, lv_color_t color);
#endif

/**********************
 *  STATIC VARIABLES
 **********************/
static dpu_handle_t handle = NULL;

static dpu_layer_cfg_t gui_layer_cfg_default = {
    .resx      = LCD_HOR_RES,
    .resy      = LCD_VER_RES,
    .colormode = DPU_COLORMODE_ARGB8888,
    .blendmode = DPU_BLENDMODE_SIMPLE,
    .startx    = 0,
    .starty    = 0,
    .sizex     = LCD_HOR_RES,
    .sizey     = LCD_VER_RES,
    .alpha     = 0,
    // .baseaddr  = (void *)LV_GUI_BUF_ADDR,
};
/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_port_disp_init(void)
{
    lvgl_init();
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    lv_disp_dev_init();
     
    /*-----------------------------
     * Create a buffer for drawing
     *----------------------------*/
    

    /* LittlevGL requires a buffer where it draws the objects. The buffer's has to be greater than 1 display row
     *
     * There are three buffering configurations:
     * 1. Create ONE buffer with some rows: 
     *      LittlevGL will draw the display's content here and writes it to your display
     * 
     * 2. Create TWO buffer with some rows: 
     *      LittlevGL will draw the display's content to a buffer and writes it your display.
     *      You should use DMA to write the buffer's content to the display.
     *      It will enable LittlevGL to draw the next part of the screen to the other buffer while
     *      the data is being sent form the first buffer. It makes rendering and flushing parallel.
     * 
     * 3. Create TWO screen-sized buffer: 
     *      Similar to 2) but the buffer have to be screen sized. When LittlevGL is ready it will give the
     *      whole frame to display. This way you only need to change the frame buffer's address instead of
     *      copying the pixels.
     * */

    /* Example for 1) */
	gui_layer_cfg_default.baseaddr = aos_malloc(LCD_HOR_RES * LCD_VER_RES* sizeof(lv_color_t));
    if(gui_layer_cfg_default.baseaddr == NULL) {
        printf("%s:%d malloc failed\n", __FUNCTION__, __LINE__);
		return;
    }
    ver_buf = (lv_color_t *)aos_malloc(LCD_HOR_RES * LCD_VER_RES* sizeof(lv_color_t));                      /*A buffer for 10 rows*/
	if(ver_buf == NULL)
	{
		printf("%s:%d malloc failed\n", __FUNCTION__, __LINE__);
		return;
	}
	
    static lv_disp_buf_t disp_buf_1;
    static lv_color_t *buf1_1 = NULL;
    buf1_1 = (lv_color_t *)aos_malloc(LCD_HOR_RES * LCD_VER_RES* sizeof(lv_color_t));                      /*A buffer for 10 rows*/
	if(buf1_1 == NULL)
	{
		printf("%s:%d malloc failed\n", __FUNCTION__, __LINE__);
		return;
	}
    lv_disp_buf_init(&disp_buf_1, buf1_1, NULL, LCD_HOR_RES * LCD_VER_RES);   /*Initialize the display buffer*/
#if 0
    /* Example for 2) */
    static lv_disp_buf_t disp_buf_2;
    static lv_color_t buf2_1[LCD_HOR_RES * 10];                        /*A buffer for 10 rows*/
    static lv_color_t buf2_2[LCD_HOR_RES * 10];                        /*An other buffer for 10 rows*/
    lv_disp_buf_init(&disp_buf_2, buf2_1, buf2_2, LCD_HOR_RES * 10);   /*Initialize the display buffer*/

    /* Example for 3) */
    static lv_disp_buf_t disp_buf_3;
    static lv_color_t buf3_1[LCD_HOR_RES * LCD_VER_RES];            /*A screen sized buffer*/
    static lv_color_t buf3_2[LCD_HOR_RES * LCD_VER_RES];            /*An other screen sized buffer*/
    lv_disp_buf_init(&disp_buf_3, buf3_1, buf3_2, LCD_HOR_RES * LCD_VER_RES);   /*Initialize the display buffer*/
#endif 

    /*-----------------------------------
     * Register the display in LittlevGL
     *----------------------------------*/

    lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/

    /*Set up the functions to access to your display*/

    /*Set the resolution of the display*/
    disp_drv.hor_res = LCD_HOR_RES;
    disp_drv.ver_res = LCD_VER_RES;

    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;

    disp_drv.rotated  =  OT_NORMAL;

    /*Set a display buffer*/
    disp_drv.buffer = &disp_buf_1;

#if 0//LV_USE_GPU
    /*Optionally add functions to access the GPU. (Only in buffered mode, LV_VDB_SIZE != 0)*/

    /*Blend two color array using opacity*/
    disp_drv.gpu_blend = gpu_blend;

    /*Fill a memory array with a color*/
    disp_drv.gpu_fill = gpu_fill;
#endif

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

/* Initialize your display and the required peripherals. */
void lv_disp_dev_init(void)
{
    // dpu init.
    //handle = DpuInit(); 
	handle = dpu_handle;
}

/**
 * @brief close layer  
 */
void lv_dpu_close_layer(uint8_t layer)
{
    csi_dpu_layer_enable(handle, layer, 0);
}

static void lv_buf_rotate(const lv_area_t * area, const lv_color_t * in_buf, lv_color_t * rot_out) {
    int sx = area->x2 - area->x1 + 1;
    int sy = area->y2 - area->y1 + 1;

    for (int j = 0 ; j < sy ; j++) {
        for (int i = 0 ; i < sx ; i++) {
            if (*(uint32_t*)&in_buf[j * sx + i] != 0xffffffff) {
                memcpy((void *)&rot_out[i * sy + sy - 1 - j], &in_buf[j * sx + i], sizeof(lv_color_t));
            }
        }
    }
}

/* Flush the content of the internal buffer the specific area on the display
 * You can use DMA or any hardware acceleration to do this operation in the background but
 * 'lv_disp_flush_ready()' has to be called when finished. */
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/

    dpu_layer_cfg_t cfg;
    const uint8_t layer_id = 3;
    static bool not_init = true;
    //static lv_color_t ver_buf[800*480];
    //static lv_color_t ver_buf[LCD_HOR_RES * LCD_VER_RES];
	
    lv_coord_t hres = disp_drv->rotated == 0 ? disp_drv->hor_res : disp_drv->ver_res;
    lv_coord_t vres = disp_drv->rotated == 0 ? disp_drv->ver_res : disp_drv->hor_res;
    if(area->x2 < 0 || area->y2 < 0 || area->x1 > hres - 1 || area->y1 > vres - 1) {

        lv_disp_flush_ready(disp_drv);
    } else {
        memcpy(&cfg, &gui_layer_cfg_default, sizeof(dpu_layer_cfg_t));

        uint32_t sizex  = area->x2 > area->x1 ? area->x2 - area->x1 + 1 : 0;    ///< display width      on       target device
        uint32_t sizey  = area->y2 > area->y1 ? area->y2 - area->y1 + 1 : 0;    ///< display height     on       target device

        if ((sizex <= 2) || (sizey == 0)) {
            lv_disp_flush_ready(disp_drv);
            return;
        }
        csi_dpu_wait_vsync(handle);
        
        lv_color_t * flush_buf = (lv_color_t *)cfg.baseaddr;

        if (disp_drv->rotated == OT_ROTATE_R_90) {
            // vertical mode
            
            lv_buf_rotate(area, color_p, ver_buf);
            sizex = sizey;
            lv_color_t * color_p_rot = ver_buf;
            for(int32_t y = area->x1; y <= area->x2 && y < cfg.resx; y++) {
                memcpy((void *)&flush_buf[y * cfg.resx + cfg.resy - area->y2], color_p_rot, sizex * sizeof(lv_color_t));
                color_p_rot += sizex;
            }
        } else {
            // horizontal mode
            int sx = area->x2 - area->x1 + 1;
            int sy = area->y2 - area->y1 + 1;
            lv_color_t * color_p_buf = ver_buf;

            for (int j = 0 ; j < sy ; j++) {
                for (int i = 0 ; i < sx ; i++) {
                    if (*(uint32_t*)&color_p[j * sx + i] != 0xffffffff) {
                       // *(uint32_t*)&color_p_buf[j * sx + (sx - 1 - i)] = *(uint32_t*)&color_p[j * sx + i];
                        *(uint32_t*)&color_p_buf[j * sx + i] = *(uint32_t*)&color_p[j * sx + i];
                    } else {
                        //*(uint32_t*)&color_p_buf[j * sx + (sx - 1 - i)] = 0;
                        *(uint32_t*)&color_p_buf[j * sx + i] = 0;
                    }
                }
            }
            for(int32_t y = area->y1; y <= area->y2 && y < cfg.resy; y++) {
                memcpy((void *)&flush_buf[y * cfg.resx + area->x1], color_p_buf, sizex * sizeof(lv_color_t));
                color_p_buf += sizex;
            }
        }
        
        if (not_init) {
           // csi_dpu_layer_enable(handle, layer_id, 0);
            csi_dpu_layer_set_config(handle, layer_id, &cfg);
            csi_dpu_layer_enable(handle, layer_id, 1);
            not_init = false;
        }
        
    }


    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
    // EXIT_VOID();
}

/*OPTIONAL: GPU INTERFACE*/
#if 0 //LV_USE_GPU

/* If your MCU has hardware accelerator (GPU) then you can use it to blend to memories using opacity
 * It can be used only in buffered mode (LV_VDB_SIZE != 0 in lv_conf.h)*/
static void gpu_blend(lv_disp_drv_t * disp_drv, lv_color_t * dest, const lv_color_t * src, uint32_t length, lv_opa_t opa)
{
    /*It's an example code which should be done by your GPU*/
    uint32_t i;
    for(i = 0; i < length; i++) {
        dest[i] = lv_color_mix(dest[i], src[i], opa);
    }
}

/* If your MCU has hardware accelerator (GPU) then you can use it to fill a memory with a color
 * It can be used only in buffered mode (LV_VDB_SIZE != 0 in lv_conf.h)*/
static void gpu_fill_cb(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
                    const lv_area_t * fill_area, lv_color_t color)
{
    /*It's an example code which should be done by your GPU*/
    uint32_t x, y;
    dest_buf += dest_width * fill_area->y1; /*Go to the first line*/

    for(y = fill_area->y1; y < fill_area->y2; y++) {
        for(x = fill_area->x1; x < fill_area->x2; x++) {
            dest_buf[x] = color;
        }
        dest_buf+=dest_width;    /*Go to the next line*/
    }
}

#endif  /*LV_USE_GPU*/

#else /* Enable this file at the top */

/* This dummy typedef exists purely to silence -Wpedantic. */
typedef int keep_pedantic_happy;
#endif
